#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/thread.hpp>
#include <boost/utility.hpp>
#include <boost/function.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
#include <boost/config/warning_disable.hpp>
#include <boost/network/include/http/server.hpp>
#include <boost/network/utils/thread_pool.hpp>
#include <boost/range/algorithm/find_if.hpp>


namespace net = boost::network;
namespace http = boost::network::http;
namespace utils = boost::network::utils;

struct async_hello_world;
typedef http::async_server<async_hello_world> server;

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <list>

using namespace std;
using boost::asio::deadline_timer;
using boost::asio::ip::tcp;
using boost::asio::ip::udp;
using boost::format;

#include "server//static_config.h"
#include "server//config_file.h"

#include "server//aes.h"

//#include "server//http_server.h"
int http_start();
typedef map<string, string> key_value;

#include "server//rad_server.h"
#include "server//user_info.h"
#include "server//keep_alive.h"
#include "server//authentication.h"
#include "server//server_main.h"


///// static field definitions

//struct aes_ctx c_aes::s_aes;
class config_file *
	config_file::instance;

boost::asio::io_service io_service;

c_aes c_aes::default_aes;

boost::mutex 
	user_info::monitor;
set<user_server_id> 
	user_info::reserve_id_;
set<user_server_id> 
	user_info::free_id_;
map<user_server_id, user_info_ptr> 
	user_info::connecting_user_;
map<user_server_id, user_info_ptr> 
	user_info::online_user_;
boost::asio::io_service& 
	user_info::io_service_ = io_service;	

list<class rad_server *> 
	rad_server::server_list;
map<void *, rad_packet_ptr> 
	rad_server::packet_list;
fr_packet_list_t *
	rad_server::fr_pl = NULL;
boost::asio::io_service& 
	rad_server::io_service_ = io_service;	

boost::asio::io_service& 
	rad_packet::io_service_ = io_service;	

void
extern_get_users(server::connection_ptr &connection)
{
	io_service.post(boost::bind(&user_info::admin_get_users, connection));
}
void
extern_kick_users(server::connection_ptr &connection, key_value &para)
{
	io_service.post(boost::bind(&user_info::admin_kick_users, connection, para));
}
void
extern_send_message(server::connection_ptr &connection, key_value &para)
{
	io_service.post(boost::bind(&user_info::admin_send_message, connection, para));
}
void
extern_set_rate(server::connection_ptr &connection, key_value &para)
{
	io_service.post(boost::bind(&user_info::admin_set_rate, connection, para));
}


// class will be deleted after this call
void 
rad_packet::check_deadline()
{
	if (wait_reply_deadline_.expires_at() <= deadline_timer::traits_type::now())
	{
		cout << "time out for packet" << endl;
		server_->failed_send();
		rad_packet_ptr p = shared_from_this();

		if (tries_ < max_tries_)
		{			
			server_->real_send(p);
			start_timer();
			return;
		}
		else if (tried_servers_.size() < (unsigned int)max_tried_servers_)
		{
			if (rad_server *s = rad_server::get_best_server(p->tried_servers_))
			{
				rad_server::remove_packet(p);
				s->send_packet(p);
				return;
			}
			else
			{
				cout << "no good server to use!" << endl;
			}
		}		
		rad_server::remove_packet(p);
		if (p->request_ && p->request_->code == PW_AUTHENTICATION_REQUEST)
			p->rad_reply_code_ = AUTH_TIMEOUT;
		else
			p->rad_reply_code_ = ACCOUNT_TIMEOUT;
		if (call_back_ != NULL) call_back_(p);
		return;
	}
}

void change_to_daemon()
{
    pid_t pid, sid;
    /* Fork off the parent process */
    pid = fork();
    if (pid < 0)
    {
        exit(EXIT_FAILURE);
    }
    /* If we got a good PID, then
       we can exit the parent process. */
    if (pid > 0)
    {
        exit(EXIT_SUCCESS);
    }
    /* Change the file mode mask */
    umask(0);
    sid = setsid();
    if (sid < 0)
    {
        exit(EXIT_FAILURE);
    }
    if ((chdir("/")) < 0)
    {
        exit(EXIT_FAILURE);
    }
    /* Close out the standard file descriptors */
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
}
void
usage()
{
    printf("My radius client : Usage\n");
    printf("-d : start as daemon\n");
	printf("-f : config file\n");
    exit(1);
}

int main(int argc, char **argv)
{
	int daemon = 0;  
    int ch;
	string filename("default.conf");

    while ((ch = getopt(argc, argv, "dhf:")) != -1)
    {
        switch (ch)
        {
        case 'h':
            usage();
        case 'd':
            daemon = 1;
            break;
        case 'f':
            filename = string(optarg);
            break;
        default:
            usage();
        }
    }
    config_file cf(filename);
	if (!cf.read_config_file()) exit(1);
	if (dict_init("./", RADIUS_DICTIONARY) < 0)
	{
		cout << "[radius client init]error read dictionary" << endl;
		return 0;
	}
		
    if (daemon == 1)
    {
        fprintf(stdout, "Server ready to start. Now switch to daemon model...\n");
        change_to_daemon();
    }
	else
	{
		fprintf(stdout, "\n.....Start my new nas radius server.....\n");
	}

    FILE * fp;
    char *charbuffer;
    void * charn;

    fp = popen("ifconfig|grep -o -P '(?<=inet addr:)(?!127)\\b([0-9]{1,3}\\.){3}[0-9]{1,3}' -m 1","r");
    charbuffer = (char *)malloc(64);
    charn = fgets(charbuffer,64,fp);
	string local_ipv4(charbuffer);
    pclose(fp);

    fp = popen("ifconfig|grep -o -P '(?<=inet6 addr:\\s)(?!fe80)(?!::)[0-90-z:]*' -m 1","r");
    charbuffer = (char *)malloc(64);
    charn = fgets(charbuffer,64,fp);
	string local_ipv6(charbuffer);
    pclose(fp);

	cf.config_vp["local_ipv4"] = local_ipv4;
	cf.config_vp["local_ipv6"] = local_ipv6;


	try
	{
		user_info::init();		

		http_start();

		rad_server::rad_init();

		if (cf.value_yes("do_radius_test"))
			rad_server::rad_test();
		
		server_main ser(io_service);

		io_service.run();
	}
	catch (std::exception& e)
	{
		std::cerr << "Exception: " << e.what() << "\n";
	}
}




static int fr_inaddr_any(fr_ipaddr_t *ipaddr)
{
    if (ipaddr->af == AF_INET)
    {
        if (ipaddr->ipaddr.ip4addr.s_addr == INADDR_ANY)
        {
            return 1;
        }
    }
    else if (ipaddr->af == AF_INET6)
    {
        if (IN6_IS_ADDR_UNSPECIFIED(&(ipaddr->ipaddr.ip6addr)))
        {
            return 1;
        }
    }
    else
    {
        cout << "[radius lib]Unknown address family" << endl;
        return -1;
    }
    return 0;
}


int fr_packet_list_id_alloc_by_socket(fr_packet_list_t *pl,
                                      RADIUS_PACKET *request)
{
    int i, id, start;
    int src_any = 0;
    uint32_t free_mask;
    fr_packet_dst2id_t my_pd, *pd;
    fr_packet_socket_t *ps;
	//cout << "0";
    if (!pl || !pl->alloc_id || !request) return 0;
	//cout << "1";
	/*
     *	Error out if no destination is specified.
     */
    if ((request->dst_ipaddr.af == AF_UNSPEC) ||
            (request->dst_port == 0))
    {
        cout << "[radius lib]No destination address/port specified" << endl;
        return 0;
    }

    /*
     *	Special case: unspec == "don't care"
     */
    if (request->src_ipaddr.af == AF_UNSPEC)
    {
        memset(&request->src_ipaddr, 0, sizeof(request->src_ipaddr));
        request->src_ipaddr.af = request->dst_ipaddr.af;
    }

    src_any = fr_inaddr_any(&request->src_ipaddr);
	
    if (src_any < 0) return 0;
	//cout << "2";
    /*
     *	MUST specify a destination address.
     */
    if (fr_inaddr_any(&request->dst_ipaddr) != 0) return 0;
	//cout << "3";
    my_pd.dst_ipaddr = request->dst_ipaddr;
    my_pd.dst_port = request->dst_port;

    pd = (fr_packet_dst2id_t *)fr_hash_table_finddata(pl->dst2id_ht, &my_pd);
    if (!pd)
    {
        pd = (fr_packet_dst2id_t *)malloc(sizeof(*pd) + 255 * sizeof(pd->id[0]));
        if (!pd) return 0;

        memset(pd, 0, sizeof(*pd) + 255 * sizeof(pd->id[0]));

        pd->dst_ipaddr = request->dst_ipaddr;
        pd->dst_port = request->dst_port;

        if (!fr_hash_table_insert(pl->dst2id_ht, pd))
        {
            free(pd);
            return 0;
        }
    }
	//cout << "4";
    /*
     *	FIXME: Go to an LRU system.  This prevents ID re-use
     *	for as long as possible.  The main problem with that
     *	approach is that it requires us to populate the
     *	LRU/FIFO when we add a new socket, or a new destination,
     *	which can be expensive.
     *
     *	The LRU can be avoided if the caller takes care to free
     *	Id's only when all responses have been received, OR after
     *	a timeout.
     */
    id = start = (int) fr_rand() & 0xff;

    while (pd->id[id] == pl->mask)   /* all sockets are using this ID */
    {
        //redo:
        id++;
        id &= 0xff;
        if (id == start) return 0;
    }
	//cout << "5";
    free_mask = ~((~pd->id[id]) & pl->mask);

    start = -1;
    for (i = 0; i < 32; i++)
    {
		//cout << i << " socket " << pl->sockets[i].sockfd << endl;
        if (pl->sockets[i].sockfd == -1) continue; /* paranoia */		
        ps = &(pl->sockets[i]);

        /*
         *	We're sourcing from *, and they asked for a
         *	specific source address: ignore it.
         */
        if (ps->inaddr_any && !src_any) continue;
        /*
         *	We're sourcing from a specific IP, and they
         *	asked for a source IP that isn't us: ignore
         *	it.
         */
        if ((fr_ipaddr_cmp(&request->src_ipaddr,
                           &ps->ipaddr) != 0)) continue;

        if ((free_mask & (1 << i)) == 0)
        {
            start = i;
            break;
        }
    }

    if (start < 0) return 0; /* bad error */
	//cout << "5" << endl;
    pd->id[id] |= (1 << start);
    ps = &pl->sockets[start];

    ps->num_outgoing++;
    pl->num_outgoing++;

    /*
     *	Set the ID, source IP, and source port.
     */
    request->id = id;
    request->sockfd = ps->sockfd;
    request->src_ipaddr = ps->ipaddr;
    request->src_port = ps->port;

    return 1;
}